 /**
 * DISCLIMER: 
 * this library is an opensource, feel free to edit or redistribute it
 * but use it with you own risk
 * 
 * ktutnik
 */
package org.ticlib.navigator
{
	import flash.display.DisplayObject;
	import flash.events.Event;

	import mx.collections.ArrayCollection;
	import mx.containers.VBox;
	import mx.core.Container;
	import mx.events.BrowserChangeEvent;
	import mx.events.FlexEvent;
	import mx.managers.IBrowserManager;
	import mx.utils.URLUtil;
	
	import org.ticlib.events.ConfirmationEvent;
	import org.ticlib.events.NavigationEvent;
	import org.ticlib.events.NotificationEvent;

	/**
	 * Requirement: 
	 * 	- BrowserManager
	 * 	- AuthController
	 * 
	 * */
	[DefaultProperty("pages")]
	public class NavigatorContainer extends VBox
	{
		//-------------- declaration
		public var usePrettyUrl:Boolean;
		protected var forceLoadData:Boolean = false;
		private var flashStack:Array;
		
		private const FRAGMENT_SEPRATOR:String = "&";
		private const PAGE_QUERY_STRING:String = "_page";

		//--------------- Properties
		private var _currentUser:PresistentGlobals;
		
		[Bindable("currentUserChange")]
		public function get globals():PresistentGlobals
		{
			return _currentUser;
		}
		
		public function set globals(val:PresistentGlobals):void
		{
			_currentUser = val;
			dispatchEvent(new Event("currentUserChange"));
		}

		private var _browserManager:IBrowserManager;
		
		[Bindable("browserManagerChange")]
		public function get browserManager():IBrowserManager
		{
			return _browserManager;
		}
		
		public function set browserManager(val:IBrowserManager):void
		{
			_browserManager = val;
			if(val)
			{
				val.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE,
					onBrowserUrlChange, false, 0, true);
				val.init();
			} 
			dispatchEvent(new Event("browserManagerChange"));
		}

		private var _pages:Array;
		
		[ArrayElementType("org.ticlib.navigator.PageDescriptor")]
		[Bindable("pagesChange")]
		public function get pages():Array
		{
			return _pages;
		}
		
		public function set pages(val:Array):void
		{
			_pages = val;
			dispatchEvent(new Event("pagesChange"));
		}
		
		private var _fragmentIndex:Number;
		
		[Bindable("fragmentIndexChange")]
		public function get fragmentIndex():Number
		{
			return _fragmentIndex;
		}
		
		public function set fragmentIndex(val:Number):void
		{
			_fragmentIndex = val;
			dispatchEvent(new Event("fragmentIndexChange"));
		}

		//----------------- contrturctor
		public function NavigatorContainer()
		{
			super();
			flashStack = new Array();
		}
		
		private function findPage(state:String):PageDescriptor
		{
			for each(var page:PageDescriptor in pages)
			{
				if(page.state == state) return page;
			}
			return null;
		}
		
		private function getLoginPage():PageDescriptor
		{
			for each(var page:PageDescriptor in pages)
			{
				if(page.isLogin) return page;
			}
			return null;
		}
		
		private function getDefaultPage():PageDescriptor
		{
			for each(var page:PageDescriptor in pages)
			{
				if(page.isDefault) return page;
			}
			return null;
		}
		
		private function getCurrentPage():PageDescriptor
		{
			return findPage(globals.currentPage);
		}
		
		private function showPage(page:PageDescriptor, params:Object):void
		{
			globals.currentPage = page.state;
			if(numChildren > 0) 
			{
				var inst:DisplayObject = getChildAt(0);
				if(inst is Container)
		
				removeAllChildren();
			}
			var instance:DisplayObject = new page.className();
			instance.addEventListener(FlexEvent.CREATION_COMPLETE, onPageCreated, false, 0, true);
			addChild(instance);
			browserManager.setTitle(page.title);
		}
		
		/**
		 * Fungsi ini memastikan bahwa PageModel sudah diinject ke page
		 * */
		private function onPageCreated(e:Event):void
		{
			var instance:DisplayObject = e.target as DisplayObject;
			var page:PageDescriptor = findPage(globals.currentPage);
			var params:Object = getObjectFromUrl();
			
			if(page.loadFunction)
				instance[page.loadFunction](params); 
			instance.removeEventListener(FlexEvent.CREATION_COMPLETE, onPageCreated);
			//if(numChildren > 1) removeChildAt(0);
		}

		/**
		 * melakukan parsing url ke bentuk objek
		 * */
		private function getObjectFromUrl():Object
		{
			var fragment:String = browserManager.fragment;
			if(!usePrettyUrl)
			{
				return URLUtil.stringToObject(fragment, 
					FRAGMENT_SEPRATOR);
			}
			else
			{
				var tokens:Array = Helper.stringSplit(fragment, "/");
				if(tokens.length <= 0) return new Object(); //tidak ada parameter
				var result:Object = new Object();
				var page:Object = findPage(tokens[0].toString());
				var mappings:Array = Helper.stringSplit(page.mapping.toString(), "/");
				if(tokens.length > mappings.length) 
				{
					dispatchEvent(NotificationEvent.warning("Incorect url parameter(s)"));
					return new Object(); //parameter yang di isi terlalu banyak
				}
				result[PAGE_QUERY_STRING] = tokens[0];
				if(tokens.length == 1) return result; //page tanpa parameter
				for(var i:int = 0; i < tokens.length; i++)
				{
					result[mappings[i]] = tokens[i + 1];
				}
				return result; //page lengkap
			}
		}
		
		/**
		 * melakukan penulisan parameter objek ke url
		 * */
		private function writeObjectToUrl(pageName:String, params:Object):void
		{
			if(!usePrettyUrl)
			{
				params[PAGE_QUERY_STRING] = pageName;
				browserManager.setFragment(URLUtil.objectToString(params, 
					FRAGMENT_SEPRATOR));
			}
			else
			{
				var fragment:String = pageName;
				var page:Object = findPage(pageName);
				var mappings:Array = Helper.stringSplit(page.mapping.toString(), "/");
				for(var i:int = 0; i < mappings.length-1; i++)
				{
					fragment += "/" + (params[mappings[i]] ? params[mappings[i]] : "");
				}
				browserManager.setFragment(fragment);
			}
		}
		
		/**
		 * digunakan oleh sistem untuk melakukan perubahan page
		 * jika url broser berubah
		 * */
		private function onBrowserUrlChange(e:Event):void
		{
			var params:Object = getObjectFromUrl();
			tryShowPage(params[PAGE_QUERY_STRING], params, forceLoadData);
		}
		
		
		/**
		 * 		- jika page tidak terdaftar otomatis di redirect ke default page
		 * 		- jika page perlu authentic, otomatis akan di redirect ke login page.
		 * 		  login page otomatis melakukan flash, sehingga akan otomatis kembali
		 * 		  ke page sebelumnya.
		 * */
		private function tryShowPage(state:String, params:Object=null, askStopProcess:Boolean=true):void
		{
			if(pages.length == 0) return;
			var page:PageDescriptor = state ? findPage(state) : getDefaultPage();
			
			//if page not available
			if(!page)
			{
				stop(); //stop current progress
				dispatchEvent(NotificationEvent.warning("Page not found"));
				page = getDefaultPage();
			}
			
			//ask for stoping current progress
			if(askStopProcess && isCurrentPageIsLoading())
			{
				var descriptor:PageDescriptor = getCurrentPage();
				var e:NavigationEvent = NavigationEvent.create(state, params);
				e.askStopCurrentProgress = false;
				dispatchEvent(ConfirmationEvent.warning(descriptor.title + "{0} is in loading progress and not " + 
						"safe to navigate to other page. do you want to stop the progress?", e));
				return;
			}
			
			//stop current page progress
			stop();
			
			//page not requires auth or user already auhtenticated
			if((page.isLogin) || (!page.roles) || (new ArrayCollection(page.roles).contains(globals.role)))
			{
				//jangan ditampilkan lagi kalau page dalam keadaan sudah tampil
				if(globals.currentPage != page.state) showPage(page, params);
				return;
			}
			
			//auth required, flash it and go to login
			dispatchEvent(NotificationEvent.warning("Login is required"));
			//simpan redirect yang sudah ada
			//nanti akan dikembalikan lagi oleh form login
			//dengan fungsi restore flash
			flashStack.push({state:state, params:params});
			showPage(getLoginPage(), null);
		}
		
		private function isCurrentPageIsLoading():Boolean
		{
			if(numChildren == 0) return false;
			var curPage:DisplayObject = getChildAt(0);
			var descriptor:PageDescriptor = getCurrentPage();
			if(!curPage) return false;
			if(!descriptor.isLoadingFunction) return false;
			if(!curPage[descriptor.isLoadingFunction]) return false;
			return curPage[descriptor.isLoadingFunction]();
		}
		
		/** 
		 * digunakan untuk merestore bookmark
		 * dipanggil pada saat creation complete aplikasi
		 * (sudah diatur oleh navigationeventmap.mxml
		 * */
		public function restoreBookmark():void
		{
			var params:Object = getObjectFromUrl();
			tryShowPage(params[PAGE_QUERY_STRING], params);
		}
		
		/**
		 * digunakan untuk navigasi page
		 * */
		public function show(e:NavigationEvent):void
		{
			if(!e.params) e.params = new Object();
			writeObjectToUrl(e.page, e.params);
			tryShowPage(e.page, e.params, e.askStopCurrentProgress);
		}
		
		/**
		 * Menyimpan (flash) nilai sebuah page, sehingga nanti bisa dipakai lagi
		 * */
		public function saveCurrentPage():void
		{
			var obj:Object = getObjectFromUrl();
			var page:String = obj[PAGE_QUERY_STRING];
			var params:Object = obj;
			flashStack.push({state:page, params:params});
		}
		
		public function restorePage():void
		{
			if(flashStack.length == 0) return;
			var page:Object = flashStack.pop();
			tryShowPage(page.state, page.params)
		}
		
		public function reload():void
		{
			if(numChildren == 0) return;
			var page:DisplayObject = getChildAt(0);
			var descriptor:PageDescriptor = getCurrentPage();
			var params:Object = getObjectFromUrl();
			if(!descriptor) return;
			if(!descriptor.loadFunction) return;
			page[descriptor.loadFunction](params);
		}
		
		public function stop():void
		{
			//page yang belum diinject dengan model
			//biasanya akan throw exception karena pengecekan fungsi isloading milik model
			//tapi sebenarnya page yang belum diinject tidak perlu di stop.
			//try catch hanya bertujuan untuk menghilangkan error. tanpa perlu penanganan lebih lanjut
			//error hanya akan terjadi pada saat startup
			try
			{
				if(numChildren == 0) return;
				var page:DisplayObject = getChildAt(0);
				var descriptor:PageDescriptor = getCurrentPage();
				if(!descriptor) return;
				if(!descriptor.isLoadingFunction) return;
				if(!page[descriptor.isLoadingFunction]()) return;
				if(!descriptor.stopFunction) return;
				page[descriptor.stopFunction]();
				dispatchEvent(NotificationEvent.error("Loading progress stopped"));
			} 
			catch(e:Error){}
		}
	}
}